/*-------------------------------------------*
 |  VisualK-OS FBA: Light Subsystem Manager  |
 |  Author: Kevin Slaughter                  |
 | Version: 3.0                              |
 *-------------------------------------------*/

/* Revision History
    v3.0: Initial standardization, after many untracked changes as development progressed.
*/

#ifndef LIGHTMANAGER_H
#define LIGHTMANAGER_H
  /* Includes */
  #include "CmdConstants.h"
  #include "StrobeManager.h"
  
  
  /* ###################
     ## ShiftPWM Shit ##
     ################### */
  #define SHIFTPWM_USE_TIMER2
  const int ShiftPWM_latchPin = 8;  // ST_CP on 74HC595
  const bool ShiftPWM_balanceLoad = false;  const bool ShiftPWM_invertOutputs = false;
  #include "/home/k-os/sketchbook/libraries/ShiftPWM/ShiftPWM.h"   // Include -after- pin declarations
  
  
  // LightManager class definition
  class LightManager {
    private:
      // Board setup
      unsigned int _numRGBs;          // Number of addressable RGB lamps
      unsigned int _numRegisters;     // Number of 74HC595 registers
      unsigned int _numWFs;           // Number of white flood lamps
      unsigned int _pwmFreq;          // PWM Frequency
      objLamp _lights[1];             // RGB LED lamps
      objLamp _lightsW[1];            // Board-wide White LEDs
      StrobeManager* _strobes;         // Strobe FX management
      
      
      // [Automation] Color Wall vars
      boolean _colorWall = false; int _cwPos = 0;
      boolean _cwDarkness = true;  // Blue --> Purple --> Blue loop
      boolean _cwDarkDown = false; // Inc/Dec indicator for loop
      
      
    public:
      /* Constructor */
      LightManager(unsigned int iNumRGBs, unsigned int iNumWF, unsigned int iPWMFreq) {
        // Set general info
        _numRGBs = iNumRGBs;                 // Total number of RGB lamps across all boards
        _numRegisters = (8/(iNumRGBs*3));    // 8 outputs per 74HC595, 3 pins per RGB LED strip
        _numWFs = iNumWF;                    // Number of white flood lamps
        _pwmFreq = (unsigned char)iPWMFreq;  // PWM frequency of LED driver
        _strobes = new StrobeManager(_numRGBs,_numWFs);    // Strobe automation management
        
        // Redimension array of RGB and white flood lamp objects
        realloc(_lights, _numRGBs*sizeof(objLamp));
        realloc(_lightsW, _numWFs*sizeof(objLamp));
        
        // Setup ShiftPWM driver
        ShiftPWM.SetAmountOfRegisters(_numRegisters);
        ShiftPWM.Start(_pwmFreq, (unsigned char)MAX_BRIGHTNESS);
        ShiftPWM.SetPinGrouping(1); ShiftPWM.SetAll(0);
        
        // Default 8ch RGB lamp cfg
        int iColor;
        for(unsigned int l=0; l<=iNumRGBs; l++) {
          _lights[l].iLamp = l;                  // Set index ID
          _lights[l].iState = LS_OFF;            // Initial state is OFF
          _lights[l].iOffset = (l>=4) ? 4 : 0;   // 2nd board offset by 4
          
          // Default (arbitrary) RGB lamp colors
          switch(l) {
            case 0: iColor = VKC_BLUE;   break;    case 1: iColor = VKC_GREEN;  break;
            case 2: iColor = VKC_PURPLE; break;    case 3: iColor = VKC_ORANGE; break;
            case 4: iColor = VKC_ORANGE; break;    case 5: iColor = VKC_PURPLE; break;
            case 6: iColor = VKC_GREEN;  break;    case 7: iColor = VKC_BLUE;   break;
          }
          _lights[l].hColor = VKC2HSV(iColor);  // Store default HSV value
        }
        
        /* Mark & configure white floods... This is fucked up, but it works.
           NOTE: The white flood LED strips that run board-wide between each line of RGB
                 LED strips on the FBA boards are wired to a single transistor that is
                 driven by (what would be) the 5th lamp's R channel (pin Q6) on the 2nd
                 75HC595 chip chain.
                 
                 Thus, to enable the white floods for each board, we set L4 to VKC_RED.*/
        
        // Setup board-wide white flood lamps
        _lightsW[0].iLamp = 4; _lightsW[0].iOffset = 0;
        _lightsW[1].iLamp = 8; _lightsW[1].iOffset = 4;
        _lightsW[0].hColor = VKC2HSV(VKC_RED);  // Board #1
        _lightsW[1].hColor = VKC2HSV(VKC_RED);  // Board #2
        
        
        
        // DEBUG: Test strobe manager
        _strobes->startProg();
      }
      
      
      /*-----------------------------*
       | Func: doColorWall()         |
       | Desc: Fade between colors.  |
       *-----------------------------*/
      void doColorWall() {
        if(_cwDarkness) {                  // Blue ~ Purple loop
          if(_cwDarkDown) {_cwPos--;}
                     else {_cwPos++;}
          
          if(_cwPos==0)     {_cwPos=240;}
          if(_cwPos <= 240) {_cwPos = 240; _cwDarkDown = false;}
          if(_cwPos >= 300) {_cwPos = 300; _cwDarkDown = true;}
        }
        else {                             // Full color range
          _cwPos++;
          if(_cwPos >= 300) {_cwPos = 0;}
        }
        
        // Set values
        for(int l=0; l<_numRGBs; l++) {
          if(l<4) {ShiftPWM.SetHSV(l,_cwPos,255,255);}
             else {ShiftPWM.SetHSV(l,_cwPos,255,255,4);}
        }
      }
      void stopColorWall() {_cwPos=0; _colorWall = false;}
      
      
      /*---------------------------------------------------------------*
      | Func: doFade(Light)                                            |
      | Desc: Handle light-x's fade in/out needs.                      |
      *----------------------------------------------------------------*/
      void doFade(unsigned int iL) {
        // Determine if we need to step up or down
        switch(_lights[iL].iState) {
          case LS_FADE_IN:
            if(_lights[iL].iStep >= MAX_STEP_FADE) {
              // If the maximum has been hit, set state to ON.
              _lights[iL].iState == LS_ON;          // Disables fade
              _lights[iL].iStep = MAX_STEP_FADE;    // Max-out, just in case fade keeps firing
              setLS(iL, LS_ON);               // Set fully on
            }
            else {
              _lights[iL].iStep++;  // Increment
              setBrightness(iL, _lights[iL].iStep);  // Set new brightness
            }
            break;
          case LS_FADE_OUT:
            if(_lights[iL].iStep <= 0) {
              // As above, but in the opposite direction.
              _lights[iL].iState = LS_OFF;
              _lights[iL].iStep = 0;
              setLS(iL, LS_OFF);  // Set completely off
            }
            else {
              _lights[iL].iStep--;  // Decrement
              this->setBrightness(iL, _lights[iL].iStep);  // Set lamp brightness level
            }
            break;
          default:  // Irrelevant lamp state... Exit.
            break;
        }
      }
      
      
      /*---------------------------------------------------------------*
       | Func: flashLED(iTimes, iMS, iColor):                          |
       | Desc: Flash an entire FBA board (iTimes) with (iMS) delay and |
       |       (optional) temporary color. Green by default.           |
       *---------------------------------------------------------------*/
      void flashLED(unsigned int iTimes, unsigned int iMS=GENERAL_DELAY, unsigned int iColor=VKC_GREEN) {
        if(iTimes<=0 && iTimes<=50) {return;}  // Sanity checks
        if(iMS==0) {iMS = GENERAL_DELAY;}
        
        // Check for trigger values {Recursive calls}
        if(iTimes == LED_OK)   {flashLED(3,125); return;}
        if(iTimes == LED_SOS)  {
          flashLED(3,62,iColor); delay(250);
          flashLED(2,125,iColor); delay(250);
          flashLED(3,62,iColor); return;
        }
        
        // Get temp. color and proceed as parameters indicate
        HsvColor ledColor = VKC2HSV(iColor);
        for(unsigned int l=0; l<iTimes; l++) {
          // On!
          setAllLamps(LS_ON, iColor); delay(iMS);
          
          // Off!
          setAllLamps(LS_OFF, iColor); delay(iMS);
        }
      }
      
      
      /*----------------------------------*
       | Func: isOn(iLight)               |
       | Desc: Check the state of a lamp. |
       *----------------------------------*/
      bool isOn(int iL) {if(iL<_numRGBs) {return (_lights[iL].iState==LS_ON);}}
      
        
      /*-------------------------------------------------*
       | Func: setAllLamps(iState, iColor)               |
       | Desc: Set all RGB lamps to a given state/color. |
       *-------------------------------------------------*/
      void setAllLamps(unsigned int iState, unsigned int iColor=VKC_NONE) {
        this->setFlood(0, iState, iColor);  // Board #1
        this->setFlood(1, iState, iColor);  // Board #2
      }
      
      
      /*------------------------------------------*
       | Func: setBrightness(iLight, iBrightness) |
       | Desc: Set the brightness of x-lamp.      |
       *------------------------------------------*/
      void setBrightness(int iLight, int iBrightness) {
        // Sanity checks
        if(iBrightness > MAX_BRIGHTNESS) {iBrightness = 255;}
        if(iLight >= _numRGBs) {return;}  // Out of bounds
        
        // Set brightness level
        _lights[iLight].hColor.v = (long)iBrightness;
        
        // Update light, if already on.
        if(this->isOn(iLight)) {this->setLS(iLight,LS_ON,VKC_NONE);}
      }
      
      
      /*-------------------------------------*
       | Func: setColor(iLight, VKC_?)       |
       | Desc: Set the color of an RGB lamp. |
       *-------------------------------------*/
      void setColor(int iLight, HsvColor hColor) {
        // Sanity check
        if(iLight>=_numRGBs) {return;}  // Out of bounds
        
        // Set lamp color
        _lights[iLight].hColor = hColor;
        if(this->isOn(iLight)) {this->setLS(iLight, LS_ON, VKC_NONE);}  // Update color if already lit
      }
    
    
    /*-------------------------------------------------------*
     | Func: setFlood(iBoard, iState, iColor)                |
     | Desc: Set all lamps on board-x to a given state/color |
     *-------------------------------------------------------*/
    void setFlood(unsigned int iBoard, unsigned int iState, unsigned int iColor=VKC_NONE) {
      // Determine which board we're addressing, to set range of affected lamps
      unsigned int iMin = (iBoard==0) ? 0 : 4;
      unsigned int iMax = (iBoard==0) ? 3 : 7;
      
      // Determine color
      HsvColor hC;
      bool bGetLightColor = (iColor==VKC_NONE);  // If true, we will use the lamp's stored color
      if(iColor==CMD_STROBE) {iColor = _strobes->getProgColor();}  // Got strobe param
      if(!bGetLightColor)    {hC = VKC2HSV(iColor);}      // Got color param
      
      // Set state of each lamp on the given board
      for(unsigned int l=iMin; l<=iMax; l++) {
        switch(iState) {
          case LS_OFF:
            setLS(l, LS_OFF); break;
          case LS_ON:
            if(bGetLightColor) {hC = VKC2HSV(iColor);}  // Get saved lamp color, if needed.
            _lights[l].iState = LS_ON;
            setLS(l, LS_ON, hC);
            break;
        }
      }
    }
    
    
    /*---------------------------------------------------------------*
     | Func: setLS(iL, iState, iColor) / setLS(iL, iState, hsvColor) |
     | Desc: Alter a lamp's state.                                   |
     *---------------------------------------------------------------*/
    // Takes VKC_? color value
    void setLS(unsigned int iL, unsigned int iState, int iColor=VKC_NONE) {
      // if iColor is VKC_NONE, pull the lamp's stored color
      HsvColor hC = (iColor==VKC_NONE) ? _lights[iL].hColor : VKC2HSV(iColor);
      setLS(iL, iState, VKC2HSV(iColor));
    }
    
    // Takes HsvColor object for color value
    void setLS(unsigned int iL, unsigned int iState, HsvColor hC) {
      // Set light state as needed
      switch(iState) {
        // RGB lamps
        case LS_ON:
          ShiftPWM.SetHSV(_lights[iL].iLamp, hC.h, hC.s, hC.v, _lights[iL].iOffset);
          _lights[iL].iState = LS_ON; break;
        case LS_OFF:
          ShiftPWM.SetHSV(_lights[iL].iLamp, 0, 0, 0, _lights[iL].iOffset);
          _lights[iL].iState = LS_OFF; break;
      //-------------------------------------------------------------------
        // White floods
        case CMD_WF_ON:
          ShiftPWM.SetHSV(_lightsW[iL].iLamp, _lightsW[iL].hColor.h, _lightsW[iL].hColor.s, _lightsW[iL].hColor.v, _lightsW[iL].iOffset);
          _lightsW[iL].iState = LS_ON; break;
        case CMD_WF_OFF:
          ShiftPWM.SetHSV(_lightsW[iL].iLamp, 0, 0, 0, _lightsW[iL].iOffset);
          _lightsW[iL].iState = LS_OFF; break;
      }
    }
    
    
    /*-----------------------------------------------------*
     | Func: setMassCfg(iBoard, sData)                     |
     | Desc: Set all lights on board-x to VKC_xxx.         |
     | *** Protocol for sData: "1-3-5-7". We cannot use    |
     |     the same delimeter as the main serial function, |
     |     to make it kick over a complete string from     |
     |     sParam instead of requiring its own function.   |
     *-----------------------------------------------------*/
    void setMassCfg(unsigned int iBoard, String strData) {
      //Serial.println("[*] setMassCfg(): strData="+strData);
      unsigned int iParams[_numRGBs]; unsigned int iCurParam = 0;
      String strTmp = ""; char cTmp = '!'; HsvColor hTmp;
      if(strData[strData.length()]!='\n') {strData += '\n';}  // Saves it from being required in the argument passed
      
      // Parse data
      for(unsigned int c=0; c<=strData.length(); c++) {
        cTmp = strData.charAt(c);
        if(cTmp=='-') {
          iParams[iCurParam] = strTmp.toInt();
          //Serial.print("[setMassCfg] Got color: "); Serial.println(iParams[iCurParam],DEC);
          iCurParam++; strTmp = "";
        }
        else if(cTmp=='\n') {
          iParams[iCurParam] = strTmp.toInt();  // Get last param
          //Serial.print("[setMassCfg] Got color: "); Serial.println(iParams[iCurParam],DEC);
        }
        else {strTmp+=cTmp;}  // Add to buffer
      }
      
      // Got params... Assign colors
      unsigned int iO = (iBoard==0) ? 0 : 4;  // Offset index for 2nd board
      for(unsigned int m=0; m<=3; m++) {
        // Store new color
        //Serial.print("   --- Light #"); Serial.println((m+iO),DEC);
        //Serial.print("     - Assigning color: "); Serial.println(iParams[m],DEC);
        hTmp = VKC2HSV(iParams[m]);
        _lights[m+iO].hColor.h = (unsigned int)hTmp.h;
        _lights[m+iO].hColor.s = (unsigned int)hTmp.s;
        _lights[m+iO].hColor.v = (unsigned int)hTmp.v;   
        
        // Update color if lamp is lit
        if(_lights[m+iO].iState == LS_ON) {setLS((m+iO), LS_ON);}
      }
    }
     
     
    /*--------------------------------------------*
     | Func: update()                             |
     | Desc: Advances automation routines/timers. |
     *--------------------------------------------*/
    void update() {
      for(unsigned int iL=0; iL<_numRGBs; iL++) {
        // Strobes override automation routines.
        if(_strobes->enabled()) {
          _strobes->update();  // Update timer & routine state
          
          // Poll strobes for update
          Serial.println("Getting shadow lamp states");
          objLamp* strobeLamps = _strobes->getLampStates();
          if(sizeof(strobeLamps)>0) {Serial.println("Got shadow array!");}
          else {Serial.println("Shadow array was empty.");}
        }
        else {  // Strobe program not running
          // Handle fades in progress
          if((_lights[iL].iState == LS_FADE_IN) | (_lights[iL].iState == LS_FADE_OUT)) {
            this->doFade(iL);  // Advance fade timer and adjust the lamp accordingly
          }
        }
      }
    }
  };
#endif
